<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Bookmarks</title>
        <link rel="icon" href="./favicon.ico" />
        <link rel="stylesheet" href="./style.css" />
        <script src="../../common/js/vue.2.6.14.min.js"></script>
        <script src="../../common/js/macy.min.js"></script>
    </head>
    <body>
        <div id="app">
            <div class="parent">
                <div class="search-wrapper">
                    <div class="search">
                        <div class="engines">
                            <div class="engine">
                                <span class="image">
                                    <img :src="loading" :img-url="search.engine.url" :alt="search.engine.title" />
                                </span>
                                <span class="title">{{ search.engine.title }}</span>
                            </div>
                            <div class="engine-list">
                                <div
                                    class="engine"
                                    v-for="engine in search.engines"
                                    :class="engine.title === search.engine.title ? 'current' : ''"
                                    @click="changeSearchEngine(engine)"
                                >
                                    <span class="image">
                                        <img :src="loading" :img-url="engine.url" :alt="engine.title" />
                                    </span>
                                    <span class="title"> {{ engine.title }} </span>
                                </div>
                            </div>
                        </div>
                        <div class="input">
                            <input
                                ref="search"
                                type="text"
                                value=""
                                v-model="search.text"
                                :placeholder="search.placeholder"
                                @keydown="stopMoveInputSelection"
                                @keyup.enter="doSearch"
                                @keyup.down="chooseResult(undefined,true)"
                                @keyup.up="chooseResult(undefined,false)"
                                @keyup="autoComplete"
                            />
                            <div class="auto-complete" v-if="Object.keys(search.autoCompleteResult).length">
                                <div
                                    class="result"
                                    v-for="result in search.autoCompleteResult"
                                    :class="search.autoCompleteResultCurrent.url===result.url?'current':''"
                                    @mouseenter="chooseResult(result,true)"
                                    @mouseleave="chooseResult(result,false)"
                                    @click="doSearch"
                                >
                                    <div class="title">{{ result.title }}</div>
                                    <div class="url">{{ result.url }}</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="types-wrapper">
                    <div class="types" id="types">
                        <div class="type" v-for="(items,type) in bookmarks">
                            <div class="title" @click="expand(items)">{{ type }}</div>
                            <div class="bookmark" v-for="item in items" :class="item.br?'br':''" @click="jump(item.url)" v-show="item.expand">
                                <div class="title-wrapper">
                                    <div class="image">
                                        <img :src="loading" :img-url="item.favicon?item.favicon:item.url" :alt="item.title" />
                                    </div>
                                    <div class="title">{{ item.title }}</div>
                                </div>
                                <div class="url">{{ item.url }}</div>
                                <div v-if="item.desc" class="arrow"></div>
                                <div v-if="item.desc" class="tooltip">{{ item.desc }}</div>
                            </div>
                            <div class="bookmark empty" v-show="items && items[0] && !items[0].expand" @click="expand(items)">...</div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="update-bookmark-button" @click="add"></div>
            <div class="expand-bookmark-type-button" @click="expandAllTypes"></div>
            <div v-show="options.show" class="options-dialog" @click="$event.stopPropagation();">
                <div class="title">Config</div>
                <div class="body">
                    <div class="option">
                        TrueOrder:
                        <label><input type="radio" name="trueOrder" v-model="options.trueOrder" :value="true" @change="macyReInit" />Yes</label>
                        <label><input type="radio" name="trueOrder" v-model="options.trueOrder" :value="false" @change="macyReInit" />No</label>
                    </div>
                    <div class="option">
                        Clear Cache:
                        <button @click="clearCache">Clear</button>
                    </div>
                </div>
                <div class="foot">
                    <button @click="save">save</button>
                </div>
            </div>
        </div>
    </body>
    <script type="module">
        import { cachedFetch } from '../../common/js/utils/index.js';
        let app = document.getElementById('app');
        let vm = new Vue({
            el: app,
            data: () => {
                return {
                    loading:
                        '',
                    search: {
                        placeholder: 'search here',
                        text: '',
                        engine: { title: 'Google', url: 'https://www.google.com/', searchUrl: 'https://www.google.com/search?q=${keyword}' },
                        engines: [],
                        autoCompleteResult: {},
                        autoCompleteResultCurrent: {}
                    },
                    bookmarks: {},
                    expandAll: true,
                    macy: undefined,
                    options: {
                        show: false,
                        trueOrder: true
                    }
                };
            },
            mounted() {
                this.focus();
                document.body.addEventListener('click', (e) => {
                    this.search.autoCompleteResult = {};
                    this.search.autoCompleteResultCurrent = {};
                    this.options.show = false;
                });
                let bookmarksOptions = localStorage.getItem('bookmarks_options');
                if (bookmarksOptions) {
                    let options = JSON.parse(bookmarksOptions);
                    this.options.trueOrder = options.trueOrder;
                }
                this.macy = Macy({
                    container: '#types',
                    trueOrder: this.options.trueOrder,
                    waitForImages: false,
                    margin: 24,
                    columns: 5,
                    breakAt: {
                        2400: 4,
                        1850: 3,
                        1400: 2,
                        900: 1
                    }
                });
                Promise.all([cachedFetch('./bookmarks-local.json', 1), cachedFetch('./bookmarks.json', 60 * 60 * 24 * 2)]).then((responses) => {
                    let responseList = [];
                    for (let response of responses) {
                        responseList.push(response.text());
                    }
                    Promise.all(responseList).then((texts) => {
                        for (let text of texts) {
                            let bookmarks = JSON.parse(text);
                            let searchList = bookmarks['Search'];
                            if (searchList && searchList.length > 0) {
                                if (typeof searchList[0] === 'string') {
                                    searchList.shift();
                                }
                                this.search.engines = searchList;
                                this.search.engine = this.search.engines[0];
                            }
                            for (let bookmarksKey in bookmarks) {
                                let bookmarkList = [];
                                let expand = true;
                                for (let bookmark of bookmarks[bookmarksKey]) {
                                    if (typeof bookmark === 'string') {
                                        expand = false;
                                    } else {
                                        bookmark['expand'] = expand;
                                        bookmarkList.push(bookmark);
                                    }
                                }
                                this.bookmarks[bookmarksKey] = bookmarkList;
                            }
                        }
                        this.macyReInit();
                        this.$nextTick(() => {
                            this.updateIcon();
                        });
                    });
                });
            },
            methods: {
                changeSearchEngine(engine) {
                    this.search.engine = engine;
                    this.focus();
                    this.updateIcon(document.querySelector('.search-wrapper .search .engines > .engine .image img'), engine.url);
                },
                updateIcon(element, url) {
                    let elements;
                    if (element) {
                        elements = [element];
                    } else {
                        elements = document.getElementsByTagName('img');
                    }
                    for (let img of elements) {
                        let src = url ?? img.getAttribute('img-url');
                        if (src) {
                            this.getImageContent(src, 0, (imageUrl) => {
                                if (imageUrl) {
                                    img.src = imageUrl;
                                }
                            });
                        }
                    }
                },
                doSearch() {
                    let text = this.search.text;
                    if (text) {
                        if (Object.keys(this.search.autoCompleteResultCurrent).length === 0) {
                            this.jump(this.search.engine.searchUrl.replace('${keyword}', encodeURIComponent(this.search.text)));
                        } else {
                            if ((text.indexOf('!') === 0 || text.indexOf('！') === 0) && text.indexOf(' ') > 0) {
                                text = text.substr(text.indexOf(' ')).trim();
                                this.jump(this.search.autoCompleteResultCurrent.searchUrl.replace('${keyword}', text));
                            } else {
                                this.jump(this.search.autoCompleteResultCurrent.url);
                            }
                        }
                    }
                },
                autoComplete(e) {
                    let resultMap = {};
                    let text = this.search.text;
                    if (text) {
                        if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
                            return false;
                        }
                        let isQuickSearch = text.indexOf('!') === 0 || text.indexOf('！') === 0;
                        if (isQuickSearch) {
                            text = text.substr(1, text.indexOf(' ') > 0 ? text.indexOf(' ') - 1 : undefined);
                        }
                        for (let bookmarksKey in this.bookmarks) {
                            for (let bookmark of this.bookmarks[bookmarksKey]) {
                                if (isQuickSearch && !bookmark.searchUrl) {
                                    continue;
                                }
                                if (bookmark.title && bookmark.title.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
                                    resultMap[bookmark.url] = bookmark;
                                }
                                if (bookmark.desc && bookmark.desc.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
                                    resultMap[bookmark.url] = bookmark;
                                }
                                if (bookmark.url.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
                                    resultMap[bookmark.url] = bookmark;
                                }
                                if (bookmark.tags) {
                                    for (let tag of bookmark.tags) {
                                        if (tag.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
                                            resultMap[bookmark.url] = bookmark;
                                        }
                                    }
                                }
                            }
                        }
                        if (!isQuickSearch) {
                            this.search.autoCompleteResultCurrent = {};
                        }
                    } else {
                        let down;
                        if (e.key === 'ArrowDown') {
                            down = true;
                        } else if (e.key === 'ArrowUp') {
                            down = false;
                        } else {
                            return false;
                        }
                        let engines = this.search.engines;
                        for (let i = 0; i < engines.length; i++) {
                            if (engines[i].url === this.search.engine.url) {
                                let index = down ? (i + 1 < engines.length ? i + 1 : 0) : i === 0 ? engines.length - 1 : i - 1;
                                console.log(index);
                                this.changeSearchEngine(engines[index]);
                                break;
                            }
                        }
                    }
                    this.search.autoCompleteResult = resultMap;
                },
                chooseResult(result, down) {
                    if (result) {
                        if (down) {
                            this.search.autoCompleteResultCurrent = result;
                        } else {
                            this.search.autoCompleteResultCurrent = {};
                        }
                    } else {
                        const keys = Object.keys(this.search.autoCompleteResult);
                        if (keys.length > 0) {
                            if (Object.keys(this.search.autoCompleteResultCurrent).length === 0) {
                                if (down) {
                                    this.search.autoCompleteResultCurrent = this.search.autoCompleteResult[keys[0]];
                                } else {
                                    this.search.autoCompleteResultCurrent = this.search.autoCompleteResult[keys[keys.length - 1]];
                                }
                            } else {
                                for (let i = 0; i < keys.length; i++) {
                                    if (this.search.autoCompleteResultCurrent.url === keys[i]) {
                                        if (down) {
                                            if (i + 1 === keys.length) {
                                                i = -1;
                                            }
                                            this.search.autoCompleteResultCurrent = this.search.autoCompleteResult[keys[i + 1]];
                                        } else {
                                            if (i === 0) {
                                                i = keys.length;
                                            }
                                            this.search.autoCompleteResultCurrent = this.search.autoCompleteResult[keys[i - 1]];
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                },
                focus() {
                    this.$refs.search.focus();
                },
                add(e) {
                    e.stopPropagation();
                    this.options.show = !this.options.show;
                },
                getImageContent(src, index, callback) {
                    let iconSrc = this.getIcon(src, index++);
                    if (iconSrc === null) {
                        callback(null);
                        return;
                    }
                    cachedFetch(iconSrc, -1)
                        .then((response) => {
                            if (response.status === 200) {
                                if (response.headers.has('Is-Cached-Fetch')) {
                                    response.text().then((text) => {
                                        if (text.length === 118) {
                                            this.getImageContent(src, index, callback);
                                        } else {
                                            callback(text);
                                        }
                                    });
                                } else {
                                    response.blob().then((content) => {
                                        let reader = new FileReader();
                                        reader.readAsDataURL(content);
                                        reader.onloadend = () => {
                                            let base64data = reader.result;
                                            if (base64data.length === 118) {
                                                this.getImageContent(src, index, callback);
                                            } else {
                                                callback(base64data);
                                            }
                                        };
                                    });
                                }
                            }
                        })
                        .catch((e) => {
                            let image = new Image();
                            image.src = iconSrc;
                            image.onload = () => {
                                let width = image.naturalWidth;
                                if (width === 1) {
                                    index++;
                                    this.getImageContent(src, index, callback);
                                } else {
                                    try {
                                        let canvas = document.createElement('canvas');
                                        let ctx = canvas.getContext('2d');
                                        let dataURL;
                                        canvas.height = image.naturalHeight;
                                        canvas.width = image.naturalWidth;
                                        ctx.drawImage(image, 0, 0);
                                        dataURL = canvas.toDataURL('image/ico');
                                        callback(dataURL);
                                    } catch (e) {
                                        callback(iconSrc);
                                    }
                                }
                            };
                        });
                },
                getIcon(url, index) {
                    index = index ?? 0;
                    let type = index % 2;
                    index = index >> 1;
                    let array = [
                        'https://favicon.yandex.net/favicon/${path}',
                        'https://www.google.com/s2/favicons?sz=64&domain_url=${path}',
                        'https://icons.duckduckgo.com/ip2/${path}.ico'
                    ];
                    if (index >= array.length) {
                        return null;
                    }
                    let path;
                    if (type === 0) {
                        path = this.getHostname(url);
                    } else {
                        path = this.getDomain(url);
                    }
                    return array[index].replace('${path}', path);
                },
                expand(arr) {
                    for (let arrElement of arr) {
                        arrElement.expand = !arrElement.expand;
                    }
                    this.macyReInit();
                },
                expandAllTypes() {
                    this.expandAll = !this.expandAll;
                    for (let bookmarksKey in this.bookmarks) {
                        for (let bookmark of this.bookmarks[bookmarksKey]) {
                            bookmark.expand = this.expandAll;
                        }
                    }
                    this.macyReInit();
                },
                macyReInit() {
                    this.$nextTick(() => {
                        setTimeout(() => this.macy.reInit(), 0);
                    });
                },
                getDomain(url) {
                    let arr = ['.com.cn'];
                    let hostname = this.getHostname(url);
                    let domain = hostname;
                    for (let suffix of arr) {
                        if (hostname.indexOf(suffix) > 0) {
                            let sec = hostname.substr(0, hostname.lastIndexOf(suffix));
                            if (sec.indexOf('.') > 0) {
                                domain = sec.substr(sec.lastIndexOf('.') + 1) + suffix;
                            }
                            break;
                        } else {
                            let sec = hostname.substr(0, hostname.lastIndexOf('.'));
                            if (sec.indexOf('.') > 0) {
                                domain = sec.substr(sec.lastIndexOf('.') + 1) + hostname.substr(hostname.lastIndexOf('.'));
                            }
                        }
                    }
                    return domain;
                },
                getHostname(url) {
                    const { hostname } = new URL(url);
                    return hostname;
                },
                jump(url) {
                    if (url.indexOf('${keyword}') > 0) {
                        url = url.replace('${keyword}', '');
                    }
                    window.open(url, '_blank').focus();
                },
                clearCache() {
                    localStorage.removeItem('./bookmarks.json');
                    localStorage.removeItem('./bookmarks-local.json');
                },
                save() {
                    this.options.show = false;
                    localStorage.setItem(
                        'bookmarks_options',
                        JSON.stringify({
                            trueOrder: this.options.trueOrder
                        })
                    );
                    this.macyReInit();
                },
                stopMoveInputSelection(e) {
                    if (['ArrowUp', 'ArrowDown', 'Enter'].includes(e.key)) {
                        e.preventDefault();
                    }
                }
            }
        });
    </script>
</html>
